/*
 * This file is part of FinVis.
 *
 * FinVis is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FinVis is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with FinVis.  If not, see <http://www.gnu.org/licenses/>
 *
 * @author Stephen Rudolph
 */

#include <QObject>
#include <cmath>
#include <cstdio>

#include "FinException.h"
#include "FinPortfolio.h"
#ifndef FIN_VIS_TEST
#include "FinRecorder.h"
#endif

using namespace std;

extern vector<InvestmentOption> investmentOptionVector;
extern double **covarianceArray;
#ifndef FIN_VIS_TEST
extern FinRecorder recorder;
#endif

/**
 * Constructor
 */
FinPortfolio::FinPortfolio() : QObject()
        , m_currentlySelectedID(0)
        , m_maxInvestmentCount(50)
        , m_lastInvestmentID(0)
        , m_optimisticWealthArray((double **) malloc(sizeof(double*) * (MAX_YEARS + 1)))
        , m_pessimisticWealthArray((double **) malloc(sizeof(double*) * (MAX_YEARS + 1)))
        , m_hypothetical(false)
{
    //Initialize arrays
    for(int i = 0; i <= MAX_YEARS; i++)
    {
        this->m_expectedWealthArray[i] = 0.0;
        this->m_optimisticWealthArray[i] = (double*) calloc(
                    this->m_maxInvestmentCount, sizeof(double));
        this->m_pessimisticWealthArray[i] = (double*) calloc(
                    this->m_maxInvestmentCount, sizeof(double));
    }

    this->m_aggregatePercentReturn = 0.0;
    this->m_aggregatePercentRisk = 0.0;
}

/**
 * Destructor
 */
FinPortfolio::~FinPortfolio()
{
    m_investments.clear();

    for(int i = 0; i <= MAX_YEARS; i++)
    {
        free(m_optimisticWealthArray[i]);
        free(m_pessimisticWealthArray[i]);
    }

    free(m_optimisticWealthArray);
    free(m_pessimisticWealthArray);
}

/**
 * Calculates the aggregate principal: \sum_{i=1}^n{P_i}
 */
int FinPortfolio::calculateAggregatePrincipal(unsigned int investmentCount, int *investmentPrincipals)
{
    int pAggregate = 0;
    for (unsigned int i = 0; i < investmentCount; i++)
    {
        pAggregate += investmentPrincipals[i];
    }

    return pAggregate;
}

/**
 * Calculates the aggregate return: \sum_{i=1}^n{w_i R_i}
 */
double FinPortfolio::calculateAggregateReturn(unsigned int investmentCount, double *investmentReturns,
                                              double *investmentWeights)
{
    double rAggregate = 0;
    for (unsigned int i = 0; i < investmentCount; i++)
    {
        rAggregate += investmentReturns[i] * investmentWeights[i];
    }

    return rAggregate;
}

/**
 * Calculates the aggregate risk:
 * \sqrt{\sum_{i=1}^n{\sum_{j=1}^n{w_i w_j \sigma_i \sigma_j c_{i,j}}}}
 */
double FinPortfolio::calculateAggregateRisk(unsigned int investmentCount, double *investmentWeights,
                                            double *investmentRisks, double **investmentCorrelations)
{
    double aggregateVariance = 0.0;
    for (unsigned int i = 0; i < investmentCount; i++)
    {
        for (unsigned int j = 0; j < investmentCount; j++)
        {
            aggregateVariance += investmentWeights[i] * investmentWeights[j] * investmentRisks[i] *
                                 investmentRisks[j] * investmentCorrelations[i][j];
        }
    }

    return sqrt(aggregateVariance);
}

/**
 * Returns a vector of all investment IDs beginning during the given year
 */
vector<int> FinPortfolio::investmentsStartingInYear(int investmentYear)
{
    vector<int> investmentIDs;

    map<unsigned int, FinInvestment>::iterator investmentIterator;
    for(investmentIterator = m_investments.begin(); investmentIterator != m_investments.end();
            investmentIterator++)
    {
        FinInvestment *investment = &(investmentIterator->second);

        if(investment->startDate().year() == investmentYear)
        {
            investmentIDs.push_back(investmentIterator->first);
        }
    }

    return investmentIDs;
}

/**
 * Calculate the optimistic investment return
 */
double FinPortfolio::calculateOptimisticInterest(int investmentPrincipal, double investmentRisk,
                                                 double investmentReturn, int investmentDuration,
                                                 bool simpleInterest)
{
    double optimisticInterest;

    if (simpleInterest)
    {
        optimisticInterest = investmentPrincipal * (investmentReturn + (2.0 * investmentRisk)) *
                             investmentDuration;
    }
    else
    {
        optimisticInterest = investmentPrincipal * (pow(1.0 + investmentReturn + (2.0 * investmentRisk),
                                  investmentDuration) - 1);
    }

    return optimisticInterest;
}

/**
 * Calculate the optimistic investment return
 */
double FinPortfolio::calculateOptimisticInterest(FinInvestment investment)
{
    return calculateOptimisticInterest(investment.principal(), investment.standardDeviation() / 100.0,
                                       investment.expectedAnnualGrowth() / 100.0,
                                       investment.durationYears(), investment.isSimpleInterest());
}

/**
 * Calculate the pessimistic investment return
 */
double FinPortfolio::calculatePessimisticInterest(int investmentPrincipal, double investmentRisk,
                                                  double investmentReturn, int investmentDuration,
                                                  bool simpleInterest)
{
    double pessimisticInterest;

    if (simpleInterest)
    {
        pessimisticInterest = investmentPrincipal * (investmentReturn - (2.0 * investmentRisk)) *
                              investmentDuration;
    }
    else
    {
        pessimisticInterest = investmentPrincipal * (pow(1.0 + investmentReturn - (2.0 * investmentRisk),
                                   investmentDuration) - 1);
    }

    return pessimisticInterest;
}

/**
 * Calculate the pessimistic investment return
 */
double FinPortfolio::calculatePessimisticInterest(FinInvestment investment)
{
    return calculatePessimisticInterest(investment.principal(), investment.standardDeviation() / 100.0,
                                        investment.expectedAnnualGrowth() / 100.0,
                                        investment.durationYears(), investment.isSimpleInterest());
}

/**
 * Multiple a length by length square matrix by a length by 1 vector
 */
double* FinPortfolio::matrixVectorMultiply(unsigned int length, double *vector, double **matrix)
{
    double *result = (double *) malloc(sizeof(double) * length);

    for (unsigned int i = 0; i < length; i++)
    {
        result[i] = 0.0;
        for (unsigned int j = 0; j < length; j++)
        {
            result[i] += matrix[i][j] * vector[j];
        }
    }

    return result;
}

/**
 * Calculate the L1 Norm of the vector
 */
double FinPortfolio::l1Norm(unsigned int length, double *vector)
{
    double accumulator = 0.0;
    for (unsigned int i = 0; i < length; i++)
    {
        accumulator += vector[i];
    }

    return accumulator;
}

/**
 * Divide up the aggregate interest across the investments according to their
 * contributions
 */
double* FinPortfolio::reallocateInterest(unsigned int investmentCount, double aggregateInterest,
                                         double *investmentInterests, double **investmentCorrelations)
{
    double *correlatedInterests = matrixVectorMultiply(investmentCount, investmentInterests,
                                                       investmentCorrelations);

    double l1NormResult = l1Norm(investmentCount, correlatedInterests);
    double rescaleFactor = 1.0;
    if (l1NormResult != 0)
    {
        rescaleFactor = aggregateInterest / l1NormResult;
    }

    for (unsigned int i = 0; i < investmentCount; i++)
    {
        correlatedInterests[i] *= rescaleFactor;
    }

    return correlatedInterests;
}

/**
 * Calculate the individual and total variances of all investments
 */
void FinPortfolio::recalculatePortfolio()
{
    //Reset arrays
    for(int i = 0; i <= MAX_YEARS; i++)
    {
        m_liquidAssetsArray[i] = m_scenarioLiquidAssetsArray[i];
        m_expectedWealthArray[i] = m_scenarioLiquidAssetsArray[i];
    }

    //Check if there are now more investments than previously allowed
    if(m_investments.size() > m_maxInvestmentCount)
    {
        //Double array length
        m_maxInvestmentCount *= 2;

        for(int i = 0; i < MAX_YEARS + 1; i++)
        {
            free(m_optimisticWealthArray[i]);
            free(m_pessimisticWealthArray[i]);

            m_optimisticWealthArray[i] = (double*) calloc(m_maxInvestmentCount, sizeof(double));
            m_pessimisticWealthArray[i] = (double*) calloc(m_maxInvestmentCount, sizeof(double));
        }
    }

    map<unsigned int, FinInvestment>::iterator investmentIterator;
    for(investmentIterator = m_investments.begin(); investmentIterator != m_investments.end();
            ++investmentIterator)
    {
        investmentIterator->second.setOrderIndex(-1);
    }

    int investmentYear, wealthRow = 0;
    double expected;
    bool investmentStart = false, investmentEnd = false;

    //Sort by start year, reverse
    for(int year = 0; year <= MAX_YEARS; year++)
    {
        for(investmentIterator = m_investments.begin();
            investmentIterator != m_investments.end();
            ++investmentIterator)
        {
            if(year ==
               investmentIterator->second.startDate().year() - START_YEAR)
            {
                if(investmentIterator->second.orderIndex() < 0)
                    investmentIterator->second.setOrderIndex(wealthRow++);
            }
        }
    }

    /*
     * Begin new calculations
     * TODO: This will need to be modified for multi-year investments
     */
    for(int year = 0; year < MAX_YEARS; year++)
    {
        vector<int> investmentIDs = investmentsStartingInYear(START_YEAR + year);
        unsigned int investmentCount = investmentIDs.size();
        int *principals = (int*) malloc(sizeof(int) * investmentCount);
        double *returns = (double*) malloc(sizeof(double) * investmentCount);
        double *weights = (double*) malloc(sizeof(double) * investmentCount);
        double *risks = (double*) malloc(sizeof(double) * investmentCount);
        double *optimisticInterests = (double*) malloc(sizeof(double) * investmentCount);
        double *pessimisticInterests = (double*) malloc(sizeof(double) * investmentCount);
        double **correlations = (double**) malloc(sizeof(risks) * investmentCount);

        //Initialize arrays
        for (unsigned int i = 0; i < investmentCount; i++)
        {
            int investmentID = investmentIDs.at(i);
            principals[i] = m_investments[investmentID].principal();
            weights[i] = principals[i];
            returns[i] = m_investments[investmentID].expectedAnnualGrowth() / 100.0;
            risks[i] = m_investments[investmentID].standardDeviation() / 100.0;
            correlations[i] = (double*) malloc(sizeof(double) * investmentCount);

            for (unsigned int j = 0; j < investmentCount; j++)
            {
                int optionIndex = m_investments[investmentID].investmentOptionVectorIndex();
                int matchingIndex = m_investments[investmentIDs.at(j)].investmentOptionVectorIndex();
                correlations[i][j] = covarianceArray[optionIndex][matchingIndex];
            }

            optimisticInterests[i] = calculateOptimisticInterest(m_investments[investmentID]);
            pessimisticInterests[i] = calculatePessimisticInterest(m_investments[investmentID]);
        }

        //Calculate portfolio properties
        int pAggregate = calculateAggregatePrincipal(investmentCount, principals);

        //Normalize weights
        if (pAggregate > 0)
        {
            for (unsigned int i = 0; i < investmentCount; i++)
            {
                weights[i] /= (double) pAggregate;
            }
        }

        double rAggregate = calculateAggregateReturn(investmentCount, returns, weights);
        double sigmaAggregate = calculateAggregateRisk(investmentCount, weights, risks, correlations);

        if (year == MAX_YEARS - 1)
        {
            this->m_aggregatePercentReturn = rAggregate * 100.0;
            this->m_aggregatePercentRisk = sigmaAggregate * 100.0;
        }

        double iAggregateLow = calculatePessimisticInterest(
                    pAggregate, sigmaAggregate, rAggregate, 1, true);
        double iAggregateHigh = calculateOptimisticInterest(
                    pAggregate, sigmaAggregate, rAggregate, 1, true);

        m_liquidAssetsArray[year] -= pAggregate;
        double interestAccrued = pAggregate * rAggregate;
        for (unsigned int i = year + 1; i <= MAX_YEARS; i++)
        {
            m_expectedWealthArray[i] += interestAccrued;
            m_liquidAssetsArray[i] += interestAccrued;
        }

        //Subtract the expected return in order to center the risk plot
        int expected = pAggregate * rAggregate;

        double *reallocatedPessimistic = reallocateInterest(
                    investmentCount, iAggregateLow - expected, pessimisticInterests, correlations);

        double *reallocatedOptimistic = reallocateInterest(
                    investmentCount, iAggregateHigh - expected, optimisticInterests, correlations);

        for (unsigned int i = 0; i < investmentCount; i++)
        {
            int investmentID = investmentIDs.at(i);
            FinInvestment investment = m_investments[investmentID];
            int oi = investment.orderIndex();

            for (unsigned int j = year + 1; j <= MAX_YEARS; j++)
            {
                m_pessimisticWealthArray[j][oi] = reallocatedPessimistic[i];
                m_optimisticWealthArray[j][oi] = reallocatedOptimistic[i];
            }
        }

        free(principals);
        free(returns);
        free(weights);
        free(risks);
        free(optimisticInterests);
        free(pessimisticInterests);
        free(reallocatedOptimistic);
        free(reallocatedPessimistic);
        for (unsigned int i = 0; i < investmentCount; i++)
        {
            free(correlations[i]);
        }
        free(correlations);
    }

    //Propogate risk
    for(int investmentIndex = m_investments.size() - 2;
        investmentIndex >= 0;
        investmentIndex--)
    {
        for(int year = 0; year <= MAX_YEARS; year++)
        {
            m_optimisticWealthArray[year][investmentIndex] +=
                    m_optimisticWealthArray[year][investmentIndex + 1];
            m_pessimisticWealthArray[year][investmentIndex] +=
                    m_pessimisticWealthArray[year][investmentIndex + 1];
        }
    }

    if(!m_hypothetical)
        emit portfolioDataChanged();
}

/*
 Public accessor methods
 */
/**
 * Return number of investments
 */
unsigned int FinPortfolio::investmentCount()
{
    return m_investments.size();
}

/**
 * Return map of all investments in the portfolio
 */
std::map<unsigned int, FinInvestment> FinPortfolio::investments()
{
    return m_investments;
}

/**
 * Add a new default investment, return the index it is inserted at
 */
unsigned int FinPortfolio::addInvestment()
{
    FinInvestment *investment = NULL;

    for(int i = 0; i < MAX_YEARS; i++)
    {
        if(m_liquidAssetsArray[i] > 0)
        {
            investment = new FinInvestment(tr(""), 0, QDate(START_YEAR, 1, 1).addYears(i), 0);
            break;
        }
    }

    unsigned int addedAtIndex = this->selectedID();
    if(investment != NULL)
    {
        addedAtIndex = this->addInvestment(investment);
    }
    else
    {
        throw FinException();
    }

    return addedAtIndex;
}

/**
 * Add the given investment, return the index it is inserted at
 */
unsigned int FinPortfolio::addInvestment(FinInvestment *investment)
{
    m_investments[++m_lastInvestmentID] = *investment;
    
    if (m_currentlySelectedID == 0)
    {
        m_currentlySelectedID = m_lastInvestmentID;
    }

    recalculatePortfolio();
#ifndef FIN_VIS_TEST
    recorder.recordAction("AddInvestment", investment->investmentOptionVectorIndex());
#endif

    return m_lastInvestmentID;
}

/**
 * Add a new default investment and select it
 */
void FinPortfolio::addAndSelectInvestment()
{
    setSelectedID(this->addInvestment());
}

/**
 * Add the given investment and select it
 */
unsigned int FinPortfolio::addAndSelectInvestment(FinInvestment *investment)
{
    setSelectedID(this->addInvestment(investment));

    return m_currentlySelectedID;
}

/**
 * Remove all investments
 */
void FinPortfolio::removeAllInvestments()
{
    m_investments.clear();
    m_currentlySelectedID = 0;

    recalculatePortfolio();
}

/**
 * Remove the selected investment
 */
void FinPortfolio::removeSelectedInvestment()
{
    removeInvestment(m_currentlySelectedID);
}

/**
 * Remove the investment corresponding to the specified ID
 */
void FinPortfolio::removeInvestment(unsigned int investmentID)
{
    if(m_investments.size() > 0)
    {
        int removed = m_investments[investmentID].investmentOptionVectorIndex();
        int nextInvestmentIDToSelect = nextID();
        m_investments.erase(investmentID);

        if(investmentID == m_currentlySelectedID)
        {
            if(m_investments.size() > 0)
            {
                m_currentlySelectedID = nextInvestmentIDToSelect;
            }
            else
            {
                m_currentlySelectedID = 0;
            }
        }

        recalculatePortfolio();
#ifndef FIN_VIS_TEST
        recorder.recordAction("RemoveInvestment", removed);
#endif
    }
}

/**
 * Get an existing investment specified by the investment ID
 */
FinInvestment* FinPortfolio::investmentForID(unsigned int investmentID)
{
    return &(m_investments[investmentID]);
}

/**
 * Get the selected investment
 */
FinInvestment* FinPortfolio::selectedInvestment()
{
    FinInvestment *investment = NULL;

    if (m_currentlySelectedID != 0)
    {
        investment = &(m_investments[m_currentlySelectedID]);
    }

    return investment;
}

/**
 * Modify an existing investment
 */
void FinPortfolio::setInvestment(unsigned int investmentID,
                                 FinInvestment *investment)
{
    m_investments[investmentID] = *investment;
    recalculatePortfolio();
}

/**
 * Modify the selected investment
 */
void FinPortfolio::setSelectedInvestment(FinInvestment investment)
{
    m_investments[m_currentlySelectedID] = investment;
    recalculatePortfolio();
}

/**
 * Selected ID getter
 */
unsigned int FinPortfolio::selectedID()
{
    return m_currentlySelectedID;
}

/**
 * Modify selected ID
 */
void FinPortfolio::setSelectedID(unsigned int investmentID)
{
    m_currentlySelectedID = investmentID;
#ifndef FIN_VIS_TEST
    recorder.recordAction("SelectInvestment", investmentID);
#endif
    emit selectionChanged();
}

/**
 * Return the next investment after the current one
 */
unsigned int FinPortfolio::nextID()
{
    if(m_investments.size() == 1)
    {
        return 0;
    }

    int orderIndex = m_investments[m_currentlySelectedID].orderIndex();
    unsigned int nextID = 0;
    
    if (orderIndex >= m_investments.size() - 1)
        orderIndex = 0;
    else
        orderIndex++;
    
    map<unsigned int, FinInvestment>::iterator investmentIterator;
    for(investmentIterator = m_investments.begin(); investmentIterator != m_investments.end();
        investmentIterator++)
    {
        if (investmentIterator->second.orderIndex() == orderIndex)
        {
            nextID = investmentIterator->first;
            break;
        }
    }
    
    return nextID;
}

/**
 * Return the previous investment after the current one
 */
unsigned int FinPortfolio::previousID()
{
    int orderIndex = m_investments[m_currentlySelectedID].orderIndex();
    unsigned int previousID = 0;

    if (orderIndex <= 0)
        orderIndex = m_investments.size() - 1;
    else
        orderIndex++;

    map<unsigned int, FinInvestment>::iterator investmentIterator;
    for(investmentIterator = m_investments.begin(); investmentIterator != m_investments.end();
        investmentIterator++)
    {
        if (investmentIterator->second.orderIndex() == orderIndex)
        {
            previousID = investmentIterator->first;
            break;
        }
    }

    return previousID;
}

/**
 * Modify base liquid assests for this scenario
 */
void FinPortfolio::setScenarioLiquidAssets(int scenarioLiquidAssetsArray[MAX_YEARS + 1])
{
    for(int i = 0; i < MAX_YEARS + 1; i++)
        m_scenarioLiquidAssetsArray[i] = scenarioLiquidAssetsArray[i];
    recalculatePortfolio();
}

/**
 * Base liquid assests getter
 */
int FinPortfolio::liquidAssets(unsigned int yearIndex)
{
    Q_ASSERT_X(yearIndex <= MAX_YEARS, "liquidAssets", "year out of bounds.");
    return m_liquidAssetsArray[yearIndex];
}

/**
 * Expected wealth getter
 */
int FinPortfolio::expectedWealth(unsigned int yearIndex)
{
    Q_ASSERT_X(yearIndex <= MAX_YEARS, "expectedWealth", "year out of bounds.");
    return m_expectedWealthArray[yearIndex];
}

/**
 * Portfolio optimistic outcome wealth getter, values are cumulative
 */
int FinPortfolio::optimisticWealth(unsigned int yearIndex)
{
    return optimisticWealth(yearIndex, 0);
}

/**
 * Portfolio pessimistic outcome wealth getter, values are cumulative
 */
int FinPortfolio::pessimisticWealth(unsigned int yearIndex)
{
    return pessimisticWealth(yearIndex, 0);
}

/**
 * @brief Portfolio percent aggregate risk for entire investment period.
 * @return the aggregate risk as a percentage.
 */
double FinPortfolio::getAggregatePercentRisk()
{
    return this->m_aggregatePercentRisk;
}

/**
 * @brief Portfolio percent aggregate return for the entire investment period.
 * @return the aggregate return as a percentage.
 */
double FinPortfolio::getAggregatePercentReturn()
{
    return this->m_aggregatePercentReturn;
}

/**
 * Optimistic outcome wealth getter, values are cumulative
 */
int FinPortfolio::optimisticWealth(unsigned int yearIndex, unsigned int investmentID)
{
    double optimistic = 0.0;

    //If 0, get the optimistic value for the entire portfolio
    if (investmentID == 0 && m_investments.size() > 0)
    {
        Q_ASSERT_X(yearIndex <= MAX_YEARS, "optimisticWealth", "year out of bounds.");
        optimistic = m_optimisticWealthArray[yearIndex][0] + m_expectedWealthArray[yearIndex];
    }
    else if(m_investments.size() > 0)
    {
        Q_ASSERT_X(yearIndex <= MAX_YEARS, "optimisticWealth", "year out of bounds.");
        int oi = m_investments[investmentID].orderIndex();
        optimistic = m_optimisticWealthArray[yearIndex][oi] + m_expectedWealthArray[yearIndex];
    }
    else
        optimistic = m_liquidAssetsArray[yearIndex];

    return round(optimistic);
}

/**
 * Pessimistic outcome wealth getter, values are cumulative
 */
int FinPortfolio::pessimisticWealth(unsigned int yearIndex, unsigned int investmentID)
{
    double pessimistic = 0.0;

    //If 0, get the optimistic value for the entire portfolio
    if (investmentID == 0 && m_investments.size() > 0)
    {
        Q_ASSERT_X(yearIndex <= MAX_YEARS, "pessimisticWealth", "year out of bounds.");
        pessimistic = m_pessimisticWealthArray[yearIndex][0] + m_expectedWealthArray[yearIndex];
    }
    else if(m_investments.size() > 0)
    {
        Q_ASSERT_X(yearIndex <= MAX_YEARS, "pessimisticWealth", "year out of bounds.");
        int oi = m_investments[investmentID].orderIndex();
        pessimistic = m_pessimisticWealthArray[yearIndex][oi] + m_expectedWealthArray[yearIndex];
    }
    else
        pessimistic = m_liquidAssetsArray[yearIndex];

    return round(pessimistic);
}

/**
 * Principal getter
 */
int FinPortfolio::principalForInvestment(unsigned int investmentID)
{
    return m_investments[investmentID].principal();
}

/**
 * Start year getter
 */
int FinPortfolio::startYearForInvestment(unsigned int investmentID)
{
    return m_investments[investmentID].startDate().year();
}

/**
 * Duration getter
 */
int FinPortfolio::durationForInvestment(unsigned int investmentID)
{
    return m_investments[investmentID].durationYears();
}

/**
 * Investment option vector getter
 */
int FinPortfolio::investmentOptionVectorIndexForInvestment(unsigned int investmentID)
{ 
    return m_investments[investmentID].investmentOptionVectorIndex();
}

/**
 * Check if making the proposed change to the selected investment will require
 * that the user go into debt.
 */
bool FinPortfolio::changingSelectedInvestmentRequiresDebt(FinInvestment proposed)
{
    return changingInvestmentRequiresDebt(m_currentlySelectedID, proposed);
}

/**
 * Check if making the proposed change to the specified investment will require
 * that the user go into debt.
 */
bool FinPortfolio::changingInvestmentRequiresDebt(int investmentID, FinInvestment proposed)
{
    m_hypothetical = true;

    FinInvestment previous = m_investments[investmentID];

    setInvestment(investmentID, new FinInvestment(proposed));

    bool debt = false;
    for(int i = 0; i < MAX_YEARS; i++)
    {
        if(m_liquidAssetsArray[i] < 0)
            debt = true;
    }

    setInvestment(investmentID, new FinInvestment(previous));

    m_hypothetical = false;

    return debt;
}
